#Copyright 2007 New Medio.  This file is part of ActiveShipping.  See README for license and legal information.

module ActiveShipping
	class UspsGateway < Base
		attr_accessor :username, :password, :debug_mode, :test_mode

		def initialize(options = {})
			if options[:dsn]
				options = options.merge(Util.parse_dsn(options.delete(:dsn)))
			end

			@username = options[:username]
			@test_mode = options[:test_mode]
			@debug_mode = options[:debug_mode]
		end

		def self.initializer_properties
			[]
		end

		def initializer_select_options(param)
			nil
		end

		def self.requires_username?
			return true
		end

		def self.requires_password?
			return false
		end

		def carrier_code
			return self.class.carrier_code
		end

		def self.carrier_code
			return :usps
		end

		def self.carrier_name
			return "USPS"
		end

		def estimate_shipping_cost(shipping_method, origin, destination, weight, num_items = nil, value = nil, other_info = nil)
			if origin.country != 'US'
				raise "USPS Gateway can only ship from within the US"
			end

			return run_request(origin.postal_code, ActiveShipping::Util.country_map[destination.country], destination.postal_code, shipping_method, weight)
		end

		def available_shipping_methods
			self.class.available_shipping_methods
		end

		def self.available_shipping_methods
			[:ground, :priority, :express]
		end

		private

		def pounds(lbs)
			lbs.to_i
		end

		def ounces(lbs)
			ounces = ((lbs - lbs.to_i) * 16).to_i
			if ounces >= 16
				ounces = 15
			end

			return ounces
		end
		
		#from_country is assumed to be 'United States' since we are using USPS
		def run_request(from_zipcode, to_country, to_zipcode, method, weight)
			reqtype = nil

			if to_country == 'United States'
				request = "<?xml version='1.0' encoding='utf-8' ?>
<RateV3Request USERID='#{@username}'>
<Package ID='1'>
	<Service>ALL</Service>
	<ZipOrigination>#{from_zipcode[0..4]}</ZipOrigination>
	<ZipDestination>#{to_zipcode[0..4]}</ZipDestination>
	<Pounds>#{pounds(weight)}</Pounds>
	<Ounces>#{ounces(weight)}</Ounces>
	<Container>RECTANGULAR</Container>
	<Size>REGULAR</Size>
	<Machinable>false</Machinable>
</Package>
</RateV3Request>
"
				post_args = {
					'API' => UspsGateway.domestic_api_name,
					'XML' => request
				}

				data = Util.run_http_post_connection(UspsGateway.gateway_url, post_args).body

				if @debug_mode
					puts data
				end
			else
				request = "<?xml version='1.0' encoding='utf-8' ?>
<IntlRateRequest USERID='#{@username}'>
<Package ID='1'>
	<Pounds>#{pounds(weight)}</Pounds>
	<Ounces>#{ounces(weight)}</Ounces>
	<MailType>Package</MailType>
	<Country>#{to_country}</Country>
</Package>
</IntlRateRequest>
"

				post_args = {
					'API' => UspsGateway.intl_api_name,
					'XML' => request
				}

				data = ActiveShipping::Util.run_http_post_connection(UspsGateway.gateway_url, post_args).body

				if @debug_mode
					puts data
				end
			end

			return extract_price_from_response_by_external_method(method, "#{data}")
		end

		def extract_price_from_response_by_external_method(method, data)
			case method
				when :priority
					extract_price_from_response(["Priority Mail", "Priority Mail International"], data)
				when :express
					extract_price_from_response(["Express Mail", "Express Mail International (EMS)", "Global Express Guaranteed"], data)
				else
					#:ground or unknown
					extract_price_from_response(["First-Class Mail Parcel", "First-Class Mail International", "Priority Mail", "Priority Mail International"], data)
			end
		end

		def extract_price_from_response(methods, data)
			methods.each do |method|
				#Need to massage "method" to work in regexes
				meth = method.gsub("(", "\\(").gsub(")", "\\)")

				if /<MailService>\s*#{meth}\s*<\/MailService>[^<]*<Rate>([^<]*?)<\/Rate>/im =~ data
					if @debug_mode
						puts "Found #{method}"
					end
					return BigDecimal.new($1)
				end
				if /<Postage>([^<]*?)<\/Postage>\s*(<SvcCommitments>[^>]*<\/SvcCommitments>)?<SvcDescription>\s*#{meth}\s*<\/SvcDescription>/im =~ data
					if @debug_mode
						puts "Found #{method}"
					end
					return BigDecimal.new($1)
				end
			end
	
			raise "Could not find rate for #{methods.join(',')} in response: #{data}."
		end

		def self.gateway_url
			"http://production.shippingapis.com/ShippingAPI.dll"
		end

		def self.ssl_gateway_url
			"https://secure.shippingapis.com/ShippingAPI.dll"
		end

		def self.domestic_api_name
			"RateV3"
		end

		def self.intl_api_name
			"IntlRate"
		end
	end
end
ActiveShipping::Base.register_shipping_module(ActiveShipping::UspsGateway)
